package com.Hsu.mq.mqserver.core;

/**
 * @author Hsu琛君珩
 * @ClassName:Router
 * @date 2024-02-20
 * @apiNote
 * @Version: v1.0
 */

import com.Hsu.mq.common.MqException;

/**
 * 使用这个类，实现交换机的转发规则
 * 同时也借助这个类验证 bindingKey 是否合法
 */
public class Router {
    /**
     * bindingKey 构造规则：
     * 1.数字、字母、下划线
     * 2.使用 . 分割
     * 3.允许存在 * 或者 # 作为通配符，但要作为独立的分段
     *
     * @param bindingKey
     * @return
     */
    public boolean checkBindingKey(String bindingKey) {
        if (bindingKey.length() == 0) {
            //空字符串，属于合法的，比如 DIRECT / FANOUT，bindingKey 可能用不上，可以视为 ""
            return true;
        }
        //检查字符串中不能存在非法字符
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            //判断是否为大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            //判断是否为小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            //判断是否为阿拉伯数字
            if (ch >= '0' && ch <= '9') {//ASCII 中 0 为 '48'，9为 '57'
                continue;
            }
            //判断是否为 _ 或者 . 或者 * 或者 #
            if (ch == '_' || ch == '.' || ch == '*' || ch == '#') {
                continue;
            }
            return false;
        }
        //检查 * 或者 # 是否是独立的部分
        // aaa.*.bbb 是合法的，但 aaa.*b.bbb 就是非法的
        String[] words = bindingKey.split("\\.");//正则表达式，想把 . 当做原始文本匹配，在正则中通过 \. 表示，又因为在 Java 字符串中，\ 也是特殊字符，因此用 \\ 对 \ 转义，才得到 \. 再得到 .
        for (String word : words) {
            //检查 word长度 > 1 并且包含了 * 或者 # 就是非法的格式
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }
        //人为约定一下，通配符直接的相邻关系
        //1. aaa.#.#.bbb 是非法的
        //2. aaa.#.*.bbb 是非法的
        //3. aaa.*.#.bbb 是非法的
        //4. aaa.*.*.bbb 是合法的
        //这么约定是因为前三种逻辑非常繁琐，并且功能性提升不大
        for (int i = 0; i < words.length - 1; i++) {//这里要 -1 是因为我们要比较相邻两个
            //连续两个 ##
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            //# 连着 *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            //* 连着 #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        return true;
    }

    /**
     * routingKey 构造规则：
     * 1.数字、字母、下划线
     * 2.使用 . 分割
     *
     * @param routingKey
     * @return
     */
    public boolean checkRoutingKey(String routingKey) {
        if (routingKey.length() == 0) {
            //空字符串，属于合法的，比如 FANOUT，routingKey 可能用不上，可以视为 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            //判断是否为大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            //判断是否为小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            //判断是否为阿拉伯数字
            if (ch >= '0' && ch <= '9') {//ASCII 中 0 为 '48'，9为 '57'
                continue;
            }
            //判断是否为 _ 或者 .
            if (ch == '_' || ch == '.') {
                continue;
            }
            //该字符不是上述任何一种合法情况，那就为 false
            return false;
        }
        //把每个字符都检查过了，没有非法的
        return true;
    }

    /**
     * 用来判定这个消息是否可以转发给这个绑定的队列
     *
     * @param exchangeType
     * @param binding
     * @param message
     * @return
     */
    public boolean route(ExchangeType exchangeType, Binding binding, Message message) throws MqException {
        //根据不同的 exchangeType 使用不同的判定转发规则
        if (exchangeType == ExchangeType.FANOUT) {
            //如果是 FANOUT，则该交换机上的绑定的所有队列都要发送
            return true;
        } else if (exchangeType == ExchangeType.TOPIC) {
            //如果是 TOPIC，就更复杂一些
            return routeTopic(binding, message);
        } else {
            //其他情况是不存在的
            throw new MqException("[Router] 交换机类型非法！exchangeType=" + exchangeType);
        }
    }

    /**
     *
     * @param binding
     * @param message
     * @return
     */
    private boolean routeTopic(Binding binding, Message message) {
        //先把这两个 key 进行切分
        String[] bindingTokens = binding.getBindingKey().split("\\.");
        String[] routingTokens = message.getRoutingKey().split("\\.");
        //引入两个下标，指向上述两个数组
        int bindingIndex = 0;
        int routingIndex = 0;
        //for 循环更适合每次进行稳定的 +1，我们这里并不确定每次加多少，因此用 while
        while (bindingIndex < bindingTokens.length && routingIndex < routingTokens.length) {
            if (bindingTokens[bindingIndex].equals("*")) {
                //如果遇到 * 直接进行下一轮，* 可以匹配任意一个部分
                bindingIndex++;
                routingIndex++;
                continue;
            } else if (bindingTokens[bindingIndex].equals("#")) {
                //如果遇到 # 先判断有没有下一个位置
                bindingIndex++;
                if (bindingIndex == bindingTokens.length) {
                    //说明 # 后面没有内容了，匹配成功了
                    return true;
                }
                //说明 # 后面还有内容，拿着这个内容，去 routingKey 中匹配
                //findNextMatch 用来查找该部分在 routingKey 的位置
                routingIndex = findNextMatch(routingTokens, routingIndex, bindingTokens[bindingIndex]);
                if (routingIndex == -1) {
                    //说明没找到匹配的结果
                    return false;
                }
                //找到的匹配的情况，继续往后匹配
                bindingIndex++;
                routingIndex++;
            } else {
                //如果遇到普通字符串，要求两边的内容一致
                if (!bindingTokens[bindingIndex].equals(routingTokens[routingIndex])) {
                    return false;
                }
                bindingIndex++;
                routingIndex++;
            }
        }
        //判断是否双方同时到达末尾
        //比如 aaa.bbb.ccc 和 aaa.bbb 是匹配失败的
        if (bindingIndex == bindingTokens.length && routingIndex == routingTokens.length) {
            return true;
        }
        return false;
    }
    private int findNextMatch(String[] routingTokens, int routingIndex, String bindingToken) {
        for (int i = routingIndex; i < routingTokens.length; i++) {
            //这里是从 routingIndex 开始的，因为前面已经匹配过了
            if(routingTokens[i].equals(bindingToken)){
                return i;
            }
        }
        return -1;
    }
}
